import os
import re
import typing

from cachetools import LRUCache, cached
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from jinja2 import Environment, FileSystemLoader
from sqlalchemy import select
from starlette.background import BackgroundTask
from starlette.staticfiles import StaticFiles
from starlette.templating import _TemplateResponse

from core.database import DBConnect
from core.models import Config
from core.plugin import (
    get_admin_plugin_menus, get_all_plugin_module_names, PLUGIN_DIR, get_plugin_state_cache
)
from core.settings import settings
from lib.common import (
    CAPTCHA_PATH, EDITOR_PATH, get_admin_menus, read_version, StringEncrypt
)
from lib.visit import get_total_visit
from lib.template_filters import datetime_format, number_format, set_query_params
from lib.template_functions import (
    editor_macro, get_member_icon, get_member_image, get_selected,
    option_selected, option_array_checked, subject_sort_link
)

@cached(LRUCache(maxsize=128))
def get_current_theme() -> str:
    """返回当前设置的主题
    - 如果设置的主题不存在，返回默认主题

    Returns:
        str: 主题名称
    """
    default_theme = "basic"
    try:
        with DBConnect().sessionLocal() as db:
            theme = db.scalar(select(Config.cf_theme)) or default_theme
            return theme
    except Exception:
        return default_theme


def get_theme_path() -> str:
    """基本环境设置 > 返回主题的设置路径
    - 如果设置的主题不存在，返回默认主题

    Returns:
        str: 主题路径
    """
    default_theme_path = f"{TEMPLATES}/basic"

    theme = get_current_theme()
    theme_path = f"{TEMPLATES}/{theme}"

    # 检查是否存在一个真正的主题
    if not os.path.exists(theme_path):
        return default_theme_path

    return theme_path


def get_admin_theme_path() -> str:
    """经理 > 返回主题的设置路径
    - .env Lup并从文件中返回一组主题
    - 如果设置的主题不存在，返回默认主题

    Returns:
        str: 主题路径
    """
    default_theme = "basic"
    default_theme_path = f"{ADMIN_TEMPLATES}/{default_theme}"
    try:
        theme = settings.ADMIN_THEME
        theme_path = f"{ADMIN_TEMPLATES}/{theme}"

        # 检查是否存在一个真正的主题
        if not os.path.exists(theme_path):
            return default_theme_path

        return theme_path
    except Exception:
        return default_theme_path


TEMPLATES = "templates"

ADMIN_TEMPLATES = "admin/templates"
ADMIN_TEMPLATES_DIR = get_admin_theme_path()  # 管理器模板路径

class TemplateService():
# “模板服务类
# -TODO：其他部分也应该分类。
# """
    _templates_dir: str = None  # 用户模板路径

    @classmethod
    def get_templates_dir(cls) -> str:
        if cls._templates_dir is None:
            cls.set_templates_dir()

        return cls._templates_dir

    @classmethod
    def set_templates_dir(cls) -> None:
        cls._templates_dir = get_theme_path()


class UserTemplates(Jinja2Templates):
# """
# 用户Jinja2模板设置类
# -管理用户重复使用的模板设置
# -以单色模式实施
# """
    _instance = None
    _is_mobile: bool = False
    default_directories = [
        TemplateService.get_templates_dir(),
        EDITOR_PATH,
        CAPTCHA_PATH,
        PLUGIN_DIR
    ]

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(UserTemplates, cls).__new__(cls)
            cls._instance._initialized = False  # Initialization flag added
        return cls._instance

    def __init__(self,
                 context_processors: dict = None,
                 globals: dict = None,
                 env: Environment = None):
        if not getattr(self, '_initialized', False):
            self._initialized = True
            super().__init__(directory=self.default_directories,
                             context_processors=context_processors)

          #模板筛选器设置
            self.env.filters["datetime_format"] = datetime_format
            self.env.filters["number_format"] = number_format
            self.env.filters["set_query_params"] = set_query_params
        #模板全局设置
            self.env.globals["editor_macro"] = editor_macro
            self.env.globals["getattr"] = getattr
            self.env.globals["get_selected"] = get_selected
            self.env.globals["get_member_icon"] = get_member_icon
            self.env.globals["get_member_image"] = get_member_image
            self.env.globals["theme_asset"] = theme_asset
            self.env.globals["get_total_visit"] = get_total_visit
            self.env.globals["encrypt"] = StringEncrypt().encrypt

       #模板上下文处理器设置
            self.context_processors.append(self._default_context)
       #设置更多env.global
            if globals:
                self.env.globals.update(**globals.__dict__)

    def _default_context(self, request: Request):
        # Lazy import
        from lib.board_lib import render_latest_posts

        context = {
            "render_latest_posts": render_latest_posts,
        }
        return context

    def TemplateResponse(
        self,
        name: str,
        context: dict,
        status_code: int = 200,
        headers: typing.Optional[typing.Mapping[str, str]] = None,
        media_type: typing.Optional[str] = None,
        background: typing.Optional[BackgroundTask] = None,
    ) -> _TemplateResponse:
# """Jinja2Templates TemplateResponse Override
        
# 自适应&如果是移动访问，将路径重新设置为优先搜索移动模板。
# -如果mobile模板不存在，将自动使用默认模板。
# -仅当通过类变量（_is_mobile）与之前的请求相比发生更改时，才重置路径。
# -该逻辑不能由构造函数处理。
# # """
        request = context.get("request")
        is_mobile: bool = getattr(request.state, "is_mobile", False)
        if (not settings.IS_RESPONSIVE
                and self._is_mobile != is_mobile):
            # 更改路径优先级
            mobile_dir = f"{TemplateService.get_templates_dir()}/mobile"
            if is_mobile:
                self.default_directories.insert(0, mobile_dir)
            else:
                if mobile_dir in self.default_directories:
                    self.default_directories.remove(mobile_dir)

            # 模板加载器重置
            self.env.loader = FileSystemLoader(self.default_directories)
            self._is_mobile = is_mobile

        return super().TemplateResponse(
            name=name,
            context=context,
            status_code=status_code,
            headers=headers,
            media_type=media_type,
            background=background
        )


class AdminTemplates(Jinja2Templates):
# """
# 管理员Jinja2模板设置类
# -管理管理器中重复使用的模板设置
# -以单色模式实施
# """
    _instance = None
    default_directories = [ADMIN_TEMPLATES_DIR, CAPTCHA_PATH, EDITOR_PATH, PLUGIN_DIR]

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(AdminTemplates, cls).__new__(cls)
            cls._instance._initialized = False  # Initialization flag added
        return cls._instance

    def __init__(self,
                 context_processors: dict = None,
                 globals: dict = None,
                 env: Environment = None
                 ):
        if not getattr(self, '_initialized', False):
            self._initialized = True
            super().__init__(directory=self.default_directories,
                             context_processors=context_processors)

            # 模板筛选器设置  #模板筛选器设置
            self.env.filters["datetime_format"] = datetime_format
            self.env.filters["number_format"] = number_format
            self.env.filters["set_query_params"] = set_query_params
            # 模板-所有设置 #模板全局设置
            self.env.globals["editor_macro"] = editor_macro
            self.env.globals["getattr"] = getattr
            self.env.globals["get_selected"] = get_selected
            self.env.globals["get_member_icon"] = get_member_icon
            self.env.globals["get_member_image"] = get_member_image
            self.env.globals["theme_asset"] = theme_asset
            self.env.globals["get_all_plugin_module_names"] = get_all_plugin_module_names
            self.env.globals["get_plugin_state_cache"] = get_plugin_state_cache
            self.env.globals["get_admin_plugin_menus"] = get_admin_plugin_menus
            self.env.globals["option_selected"] = option_selected
            self.env.globals["option_array_checked"] = option_array_checked
            self.env.globals["subject_sort_link"] = subject_sort_link
            self.env.globals["encrypt"] = StringEncrypt().encrypt
            # 设置模板上下文处理器 #模板上下文处理器设置
            self.context_processors.append(self._default_admin_context)

            # 添加 env.global 设置
            if globals:
                self.env.globals.update(**globals.__dict__)

    def _default_admin_context(self, request: Request):
        context = {
            "admin_menus": get_admin_menus(),
            "version": read_version()
        }
        return context


def theme_asset(request: Request, asset_path: str) -> str:
    """
    当前的主题 asset url要返回的辅助函数

    Args:
        request (Request): Request 对象
        asset_path (str): 插件模块名称

    Returns:
        asset_url (str): asset url
    """
    theme = get_current_theme()

    return f"/theme_static/{theme}/{asset_path}"


def register_theme_statics(app: FastAPI) -> None:
# """
# 将当前主题的static路径注册为虚拟路径的函数
# - ex) PC: /{theme}/basic/static/css -> /theme_static/basic/css
# - ex) Mobile: /{theme}/basic/mobile/static/css -> /theme_static/basic/mobile/css

# Args:
# app（FastAPI）：FastAPI对象
# # """
    theme = get_current_theme()
    directories = ["/mobile", ""]
    for directory in directories:
        static_directory = f"{TEMPLATES}/{theme}{directory}/static"

        if not os.path.isdir(static_directory):
            # logger = logging.getLogger("uvicorn.error")
            # logger.warning("theme has not static directory : ",
            #                static_directory)
            continue

        url = f"/theme_static/{theme}{directory}"
        path = StaticFiles(directory=static_directory)
        static_device = directory.replace("/", "_")
        app.mount(url, path, name=f"static_{theme}{static_device}")  # tag


def get_theme_list():
    """返回主题目录列表作为列表
    
    Returns:
        list: 目录列表

    """
    result_array = []

    dirname = os.path.join(TEMPLATES)
    for file in os.listdir(dirname):
        if file in ['.', '..']:
            continue

        theme_path = os.path.join(dirname, file)
        require_file_list = ['readme.txt', 'index.html']
        if (os.path.isdir(theme_path)
                and all(os.path.isfile(os.path.join(theme_path, fname)) for fname in require_file_list)):
            result_array.append(file)

    result_array.sort()  # Using Python's default sort which is similar to natsort for strings

    return result_array


def get_theme_info(theme_name: str) -> dict:
    """返回主题信息.

    Args:
        theme_name (str): 主题名称

    Returns:
        dict: 主题信息
    """
    info = {}
    path = os.path.join(TEMPLATES, theme_name)

    if os.path.isdir(path):
        screenshot_url = ''
        if os.path.exists(os.path.join(path, 'screenshot.webp')):
            screenshot_url = f"/admin/screenshot/{theme_name}"

        elif os.path.exists(os.path.join(path, 'screenshot.png')):
            screenshot_url = f"/admin/screenshot/{theme_name}"

        info['screenshot'] = screenshot_url

        text = os.path.join(path, 'readme.txt')
        if os.path.isfile(text):
            with open(text, 'r', encoding="UTF-8") as f:
                content = [line.strip() for line in f.readlines()]

            patterns = [
                ("^Theme Name:(.+)$", "theme_name"),
                ("^Theme URI:(.+)$", "theme_uri"),
                ("^Maker:(.+)$", "maker"),
                ("^Maker URI:(.+)$", "maker_uri"),
                ("^Version:(.+)$", "version"),
                ("^Detail:(.+)$", "detail"),
                ("^License:(.+)$", "license"),
                ("^License URI:(.+)$", "license_uri")
            ]

            for line in content:
                for pattern, key in patterns:
                    match = re.search(pattern, line, re.I)
                    if match:
                        info[key] = match.group(1).strip()

        if not info.get('theme_name'):
            info['theme_name'] = theme_name

        info['theme_dir'] = theme_name

    return info
